package com.airport.schedule.greedy;

import com.airport.schedule.Problem;
import com.airport.schedule.ScheduleSolution;
import com.airport.schedule.model.AdHocRule;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import com.airport.schedule.util.ListUtil;
import com.airport.schedule.util.MapUtil;
import com.airport.schedule.util.SetUtil;
import edu.stanford.nlp.util.IntervalTree;

import java.util.*;

public class StageWiseGreedyScheduler extends Thread {

    private Problem problem;
    private Map<Flight, List<FlightGate>> flightAvailableGates;
    private Map<Flight, List<FlightGate>> flightNearGates;
    private Map<Flight, Double> flight2CompeteRate;
    private Map<Flight, Double> flight2NearGateCompeteRate;

    private List<Flight> unassignedFlights;

    private ScheduleSolution solution;
    private IntervalTree<Integer, Flight> intervalTree;
    private double gamma = 0.9995;


    public StageWiseGreedyScheduler(Problem problem) {
        this.problem = problem;
        this.flightAvailableGates = problem.getFlightAvailableGates(true);
        this.flightNearGates = new HashMap<>();
        this.flightAvailableGates.forEach((flight, gates)->
                flightNearGates.put(flight, ListUtil.filter(gates, FlightGate::isNearGate)));

        this.unassignedFlights = new ArrayList<>();
        this.flight2CompeteRate = new HashMap<>();
        this.flight2NearGateCompeteRate = new HashMap<>();

        this.intervalTree = new IntervalTree<>();
        List<Flight> flights = problem.getScheduleFlights();
        this.intervalTree.addAll(flights);

        flights.forEach(iFlight->{
            Set<FlightGate> iGates = new HashSet<>(flightAvailableGates.get(iFlight));
            Set<FlightGate> iNGates = new HashSet<>(flightNearGates.get(iFlight));
            List<Flight> conflictFlights = intervalTree.getOverlapping(iFlight);
            conflictFlights.remove(iFlight);

            flight2CompeteRate.put(iFlight, 0.0);
            flight2NearGateCompeteRate.put(iFlight, 0.0);
            conflictFlights.forEach(jFlight->{
                Set<FlightGate> jGates = new HashSet<>(flightAvailableGates.get(jFlight));

                Set<FlightGate> common = SetUtil.intersect(iGates, jGates);
                flight2CompeteRate.put(iFlight, flight2CompeteRate.get(iFlight)
                        + (common.size()+0.0)/jGates.size());

                Set<FlightGate> jNGates = new HashSet<>(flightNearGates.get(jFlight));
                if(!jNGates.isEmpty()) {
                    Set<FlightGate> commonN = SetUtil.intersect(iNGates, jNGates);
                    flight2NearGateCompeteRate.put(iFlight, flight2NearGateCompeteRate.get(iFlight)
                            + (commonN.size() + 0.0) / jGates.size());
                }

            });
        });
        System.out.println("StageWiseGreedyScheduler init successfully!");
    }

    public List<Flight> getUnassignedFlights() {
        return unassignedFlights;
    }

    public ScheduleSolution getSolution() {
        return solution;
    }

    @Override
    public void run() {
        greedyProcess();
    }

    public void greedyProcess() {
        this.solution = new ScheduleSolution(problem);
        // 贪心策略一：按照冲突影响率排序，冲突率小的航班先得到机位, 冲突率一样的按照机位多的先获得
        List<Flight> flights = new ArrayList<>(problem.getScheduleFlights());
        Collections.sort(flights, (f1, f2)-> {
            if(f1.isBusinessFlight() && !f2.isBusinessFlight()) {
                return 1;
            }else if(!f1.isBusinessFlight() && f2.isBusinessFlight()) {
                return -1;
            }
            if(f1.isCargoPlane() && !f2.isCargoPlane()) {
                return 1;
            }else if(!f1.isCargoPlane() && f2.isCargoPlane()) {
                return -1;
            }
            int r = Double.compare(flight2CompeteRate.get(f1), flight2CompeteRate.get(f2));
            if(r == 0) {
                int r1 = Double.compare(flight2NearGateCompeteRate.get(f1),
                        flight2NearGateCompeteRate.get(f2));
                if(r1 == 0) {
                    int a1 = flightNearGates.get(f1).size();
                    int a2 = flightNearGates.get(f2).size();
                    if(a1 == a2) {
                        int peopleNum1 = f1.getNumPeople();
                        int peopleNum2 = f2.getNumPeople();
                        return peopleNum2 - peopleNum1;
                    }
                    return a2-a1;
                }
                return r1;
            }
            return r;
        });
        // 开始分配，按照先来先得的贪心策略

        while(!flights.isEmpty()) {
            Flight flight = flights.remove(0);
            List<FlightGate> unoccupiedNormalGates = solution.getUnoccupiedNormalFlightGates(flight, true);
            if(!unoccupiedNormalGates.isEmpty()) {// 贪心策略二：先优先正常机位
                List<FlightGate> nearGates = ListUtil.filter(unoccupiedNormalGates, FlightGate::isNearGate);
                if(!nearGates.isEmpty()) {
                    FlightGate gate = findBestFlightGate(nearGates, solution, flight, flights);
                    solution.assign(flight, gate);
                }else {
                    List<FlightGate> candidates = new ArrayList<>();
                    List<FlightGate> adHocGates = solution.getAvailableAdHocFlightGates(flight);
                    candidates.addAll(ListUtil.filter(adHocGates,
                            gate -> availableAdHocGate(gate, flight, solution)));
                    candidates.addAll(unoccupiedNormalGates);
                    FlightGate gate = findBestFlightGate(candidates, solution, flight, flights);
                    if (gate.isAdHocGate()) {
                        System.out.println("提前安排临时措施: " + gate);
                    }
                    solution.assign(flight, gate);
                }
            }else{ // 没有正常机位，除非采取临时措施！
                List<FlightGate> gates = solution.getAvailableAdHocFlightGates(flight);
                if(!gates.isEmpty()) {
                    FlightGate gate = findMinImpactGate(flight, flights, gates, solution);
                    System.out.println("安排一个临时措施："+gate);
                    solution.assign(flight, gate);
                }else {
                    unassignedFlights.add(flight);
                }
            }
        }
    }

    private boolean availableAdHocGate(FlightGate adHocGate, Flight flight, ScheduleSolution solution) {
        AdHocRule rule = adHocGate.getRule();
        if(rule.getRuleType() == AdHocRule.IS_DOWN_GREDE
                || rule.getRuleType() == AdHocRule.IS_COMPOSITE) return false;
        List<Flight> mayBeAffected = intervalTree.getOverlapping(flight);
        List<Flight> unassigned = ListUtil.filter(mayBeAffected, f->!solution.isAssigned(f));
        List<FlightGate> obsGates = problem.getFlightGatesByIds(rule.getObstructFlightGates()); // 阻挡机位
        List<FlightGate> obsInGates = problem.getFlightGatesByIds(rule.getObstructRunInFlightGates()); // 阻挡滑入
        List<FlightGate> homoGates = problem.getFlightGatesByIds(rule.getHomogenyFlightGates()); // 同机位
        Set<FlightGate> affectedGates = new HashSet<>(obsGates);
        affectedGates.addAll(obsInGates);
        affectedGates.addAll(homoGates);
        for(FlightGate gate : affectedGates) {
            for(Flight unassignedFlight : unassigned) {
                if(!solution.isFull(gate, unassignedFlight)) { // 抑制临时机位
                    return false;
                }
            }
        }
        return true;
    }

    private FlightGate findMinImpactGate(Flight curFlight, List<Flight> restFlights,
                                         List<FlightGate> gates, ScheduleSolution sol) {
        List<FlightGate> preferable = ListUtil.filter(gates, gate->availableAdHocGate(gate, curFlight, sol));
        List<FlightGate> candidates = preferable.isEmpty() ? gates : preferable;

        List<FlightGate> bestGates = new ArrayList<>();
        double maxScore = Double.MIN_VALUE;
        for(FlightGate gate : candidates) {
            sol.assign(curFlight, gate);
            double score = 0.0;
            for(Flight flight : restFlights) {
                score += sol.getCurrentAvailableGates(flight, true).size();
            }
            sol.remove(curFlight, gate);
            if(maxScore == score) {
                bestGates.add(gate);
            }else if(maxScore < score){
                bestGates = new ArrayList<>();
                bestGates.add(gate);
                maxScore = score;
            }
        }
        if(bestGates.size() == 1) {
            return bestGates.get(0);
        }else{
            int minConflict = Integer.MAX_VALUE;
            FlightGate bestOfTheBest = null;
            for(FlightGate gate : bestGates) {
                sol.assign(curFlight, gate);
                int c = sol.getRunwayConflictNum();
                if(c < minConflict) {
                    minConflict = c;
                    bestOfTheBest = gate;
                }
                sol.remove(curFlight, gate);
            }
            return bestOfTheBest;
        }
    }

    private FlightGate findBestFlightGate(List<FlightGate> candidates, ScheduleSolution solution,
                                          Flight curFlight, List<Flight> unassignedFlights) {
        Map<FlightGate, Double> gateOccupiedRate = new HashMap<>();
        Map<FlightGate, Double> gateRunwayConflictRate = new HashMap<>();
        for(FlightGate g : candidates) {
            gateOccupiedRate.put(g, 0.0);
            gateRunwayConflictRate.put(g, 0.0);
        }
        Set<FlightGate> curFlightGates = new HashSet<>(candidates);
        for(Flight comingFlight : unassignedFlights) {
            Set<FlightGate> comingFlightGates =
                    new HashSet<>(solution.getUnoccupiedNormalFlightGates(comingFlight, true));
            if(SetUtil.hasIntersect(curFlightGates, comingFlightGates)) {
                for(FlightGate gate : curFlightGates) {
                    if(comingFlightGates.contains(gate)) {
                        gateOccupiedRate.put(gate,
                                gateOccupiedRate.get(gate)
                                        +1.0/comingFlightGates.size());
                    }
                }
            }
        }
        for(FlightGate gate : candidates) {
            solution.assign(curFlight, gate);
            int conflictNum = solution.getRunwayConflictNum();
            solution.remove(curFlight, gate);
            gateRunwayConflictRate.put(gate, gateRunwayConflictRate.get(gate)+conflictNum);
        }
        MapUtil.normalize(gateOccupiedRate);
        MapUtil.normalize(gateRunwayConflictRate);
        double minVal = Double.MAX_VALUE;
        List<FlightGate> bestGates = new ArrayList<>();
        for(Map.Entry<FlightGate, Double> entry : gateOccupiedRate.entrySet()) {
            double value = (1 - gamma) * entry.getValue() + gamma * gateRunwayConflictRate.get(entry.getKey());
            if(value == minVal) {
                bestGates.add(entry.getKey());
            }else if(value < minVal){
                bestGates = new ArrayList<>();
                bestGates.add(entry.getKey());
                minVal = value;
            }
        }
        return bestGates.get(0);
    }

}